# PreparedSQLAppender.java

    package org.codefilarete.stalactite.query.builder;

    import javax.annotation.Nullable;
    import java.sql.PreparedStatement;
    import java.util.HashMap;
    import java.util.Map;

    import org.codefilarete.stalactite.query.api.Fromable;
    import org.codefilarete.stalactite.query.api.Selectable;
    import org.codefilarete.stalactite.query.model.Placeholder;
    import org.codefilarete.stalactite.query.model.ValuedVariable;
    import org.codefilarete.stalactite.query.api.Variable;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.statement.PreparedSQL;
    import org.codefilarete.stalactite.sql.statement.binder.ColumnBinderRegistry;
    import org.codefilarete.stalactite.sql.statement.binder.ParameterBinder;
    import org.codefilarete.tool.trace.MutableInt;

    /**
     * An appender to a {@link PreparedSQL}
     */
    public class PreparedSQLAppender implements SQLAppender {

    	private final SQLAppender delegate;
    	private final ColumnBinderRegistry parameterBinderRegistry;
    	private final Map<Integer, ParameterBinder<?>> parameterBinders;
    	private final Map<Integer, Object> values;
    	private final MutableInt paramCounter;

    	public PreparedSQLAppender(SQLAppender sqlAppender, ColumnBinderRegistry parameterBinderRegistry) {
    		this(sqlAppender, parameterBinderRegistry, new HashMap<>(), new HashMap<>(), new MutableInt(1));
    	}

    	private PreparedSQLAppender(SQLAppender delegate,
    								ColumnBinderRegistry parameterBinderRegistry,
    								Map<Integer, ? extends ParameterBinder<?>> parameterBinders,
    								Map<Integer, Object> values,
    								MutableInt paramCounter) {
    		this.delegate = delegate;
    		this.parameterBinderRegistry = parameterBinderRegistry;
    		this.parameterBinders = (Map<Integer, ParameterBinder<?>>) parameterBinders;
    		this.values = values;
    		this.paramCounter = paramCounter;
    	}

    	public Map<Integer, Object> getValues() {
    		return values;
    	}

    	public Map<Integer, ParameterBinder<?>> getParameterBinders() {
    		return parameterBinders;
    	}

    	@Override
    	public PreparedSQLAppender cat(String s, String... ss) {
    		delegate.cat(s, ss);
    		return this;
    	}

    	/**
    	 * Implemented such it adds the value as a {@link PreparedStatement} mark (?) and keeps it for future use in the value list.
    	 *
    	 * @param column
    	 * @param value  the object to be added/printed to the statement
    	 * @return this
    	 * @param <V> value type
    	 */
    	@Override
    	public <V> PreparedSQLAppender catValue(@Nullable Selectable<V> column, Object value) {
    		ParameterBinder<?> parameterBinder;
    		if (column == null) {
    			parameterBinder = getParameterBinderFromRegistry(value);
    		} else if (column instanceof Column) {
    			parameterBinder = parameterBinderRegistry.getBinder((Column) column);
    		} else {
    			parameterBinder = parameterBinderRegistry.getBinder(column.getJavaType());
    		}
    		return catValue(value, parameterBinder);
    	}

    	@Override
    	public PreparedSQLAppender catValue(Object value) {
    		return catValue(value, getParameterBinderFromRegistry(value));
    	}

    	private ParameterBinder<?> getParameterBinderFromRegistry(Variable value) {
    		ParameterBinder<?> parameterBinder = null;
    		if (value instanceof ValuedVariable) {
    			parameterBinder = getParameterBinderFromRegistry(((ValuedVariable) value).getValue());
    		} else if (value instanceof Placeholder) {
    			parameterBinder = parameterBinderRegistry.getBinder(((Placeholder) value).getValueType());
    		}
    		return parameterBinder;
    	}
    	private ParameterBinder<?> getParameterBinderFromRegistry(Object value) {
    		ParameterBinder<?> parameterBinder;
    		if (value instanceof Variable) {
    			parameterBinder = getParameterBinderFromRegistry(((Variable) value));
    		} else {
    			Class<?> binderType = value.getClass().isArray() ? value.getClass().getComponentType() : value.getClass();
    			parameterBinder = parameterBinderRegistry.getBinder(binderType);
    		}
    		return parameterBinder;
    	}

    	private PreparedSQLAppender catValue(Object value, ParameterBinder<?> binderSupplier) {
    		if (value instanceof ValuedVariable) {
    			Object innerValue = ((ValuedVariable) value).getValue();
    			if (innerValue instanceof Column) {
    				// Columns are simply appended (no binder needed nor index increment)
    				delegate.catColumn((Column) innerValue);
    			} else {
    				appendPlaceholder(innerValue, binderSupplier);
    			}
    		} else {
    			appendPlaceholder(value, binderSupplier);
    		}
    		return this;
    	}

    	private void appendPlaceholder(Object value, ParameterBinder<?> binderSupplier) {
    		delegate.cat("?");
    		values.put(paramCounter.getValue(), value);
    		parameterBinders.put(paramCounter.getValue(), binderSupplier);
    		paramCounter.increment();
    	}

    	@Override
    	public PreparedSQLAppender catColumn(Selectable<?> column) {
    		// Columns are simply appended (no binder needed nor index increment)
    		delegate.catColumn(column);
    		return this;
    	}

    	@Override
    	public SQLAppender catTable(Fromable table) {
    		delegate.catTable(table);
    		return this;
    	}

    	@Override
    	public SQLAppender removeLastChars(int length) {
    		delegate.removeLastChars(length);
    		return this;
    	}

    	@Override
    	public String getSQL() {
    		return delegate.getSQL();
    	}

    	@Override
    	public SubSQLAppender newSubPart(DMLNameProvider dmlNameProvider) {
    		SQLAppender self = PreparedSQLAppender.this;
    		return new DefaultSubSQLAppender(new PreparedSQLAppender(
    				this.delegate.newSubPart(dmlNameProvider),
    				this.parameterBinderRegistry,
    				this.parameterBinders,
    				this.values,
    				this.paramCounter)) {
    			@Override
    			public SQLAppender close() {
    				// nothing special
    				return self;
    			}
    		};
    	}
    }